import './extendApi'
import { getStorage, clearStorage } from './storage'
import env from './env'

// 创建 WxRequest 类，1. 网络请求模块
class WxRequest {
  // 定义实例属性，用来设置默认请求参数
  defaults = {
    baseURL: '', //请求基地址
    url: '', // 接口请求路径
    data: null, // 请求参数
    method: 'GET', // 默认的请求方式
    // 请求头
    header: {
      'Content-type': 'application/json' // 设置数据的交互格式
    },
    timeout: 60000, // 默认的超时时长，小程序默认的时长为1分钟
    isLoading: true // 控制是否使用默认的 loading
  }

  // 定义拦截器对象
  interceptors = {
    // 请求拦截器
    request: (config) => config,

    // 响应拦截器
    response: (response) => response
  }

  // 定义数组队列，初始值是空数组，用来存储请求队列、存储请求标识
  queue = []

  // 用于创建和初始化类的属性及方法
  // 实例化传入的参数，会被 constructor 形参进行接收
  constructor(params = {}) {
    // 传入的参数覆盖默认的参数
    this.defaults = Object.assign({}, this.defaults, params)
  }

  // request 实例方法接收一个对象类型的参数
  request(options) {
    // 如果有新的请求，清除上一次的定时器
    this.timerId && clearTimeout(this.timerId)

    // 合并完整的请求地址 (baseURL + url)
    options.url = this.defaults.baseURL + options.url

    // 合并请求参数
    options = { ...this.defaults, ...options }

    // 在请求发送之前，调用请求拦截器
    options = this.interceptors.request(options)

    if (options.isLoading && options.method !== 'UPLOAD') {
      // 判断 queue 队列是否为空，为空则显示loading
      this.queue.length === 0 &&
        wx.showLoading({
          title: '数据加载中...'
        })

      // 向 queue 数组队列中添加请求标识
      this.queue.push('request')
    }

    // 用Promise封装 wx.request 处理异步请求
    return new Promise((resolve, reject) => {
      if (options.method === 'UPLOAD') {
        wx.uploadFile({
          ...options,

          success: (res) => {
            // 将服务器返回的JSON字符串转换为对象
            res.data = JSON.parse(res.data)

            // 数据合并
            const mergeRes = Object.assign({}, res, {
              config: options,
              isSuccess: true
            })

            resolve(this.interceptors.response(mergeRes))
          },

          fail: (err) => {
            // 数据合并
            const mergeErr = Object.assign({}, res, {
              config: options,
              isSuccess: false
            })

            reject(this.interceptors.response(mergeErr))
          }
        })
      } else {
        wx.request({
          ...options,

          // 接口调用成功触发的回调
          success: (res) => {
            // 合并数据, res: 服务器响应的数据，config: 请求参数
            const mergeRes = Object.assign({}, res, { config: options, isSuccess: true })
            resolve(this.interceptors.response(mergeRes))
          },

          // 接口调用失败触发的回调
          fail: (err) => {
            // 合并数据, err: 请求失败的报错，config: 请求参数
            const mergeErr = Object.assign({}, err, { config: options, isSuccess: false })
            resolve(this.interceptors.response(mergeErr))
          },

          // 接口调用结束触发的回调 (成功失败都会触发)
          complete: () => {
            if (options.isLoading) {
              // 清除 queue 末尾项
              this.queue.pop()
              this.queue.length === 0 && this.queue.push('request')

              // 用定时器的异步来处理接龙请求的loading框闪烁问题
              this.timerId = setTimeout(() => {
                // 清除 queue 末尾项
                this.queue.pop()
                // 判断是否为空，空表明请求完毕，隐藏 loading
                this.queue.length === 0 && wx.hideLoading()
                // 清除定时器
                clearTimeout(this.timerId)
              }, 1)
            }
          }
        })
      }
    })
  }

  // 封装 GET 实例方法
  get(url, data = {}, config = {}) {
    return this.request(Object.assign({ url, data, method: 'GET' }, config))
  }

  // 封装 POST 实例方法
  post(url, data = {}, config = {}) {
    return this.request(Object.assign({ url, data, method: 'POST' }, config))
  }

  // 封装 DELETE 实例方法
  delete(url, data = {}, config = {}) {
    return this.request(Object.assign({ url, data, method: 'DELETE' }, config))
  }

  // 封装 PUT 实例方法
  put(url, data = {}, config = {}) {
    return this.request(Object.assign({ url, data, method: 'PUT' }, config))
  }

  // 封装处理并发请求的方法
  all(...promise) {
    // 展开运算符会将传入的数据转为数组
    return Promise.all(promise)
  }

  /**
   * @description 封装数据上传的实例方法
   * @param {*} url 文件上传地址，接口地址
   * @param {*} filePath 要上传文件的资源路径
   * @param {*} name 文件对应的key
   * @param {*} config 其他配置项
   */
  upload(url, filePath, name = 'file', config = {}) {
    return this.request(Object.assign({ url, filePath, name, method: 'UPLOAD' }, config))
  }
}

// 对 WxRequest 进行实例化
const instance = new WxRequest({
  baseURL: env.baseURL,
  timeout: 10000
})

// 2. 请求响应拦截器模块
// 配置请求拦截器
instance.interceptors.request = (config) => {
  // 获取本地token
  const token = getStorage('token')

  // 如果有token将其加到请求头里
  token && (config.header.token = token)

  return config
}

// 配置响应拦截器
instance.interceptors.response = async (response) => {
  // 从response 中解构出需要的数据
  const { isSuccess, data } = response

  // 请求失败打印失败信息
  if (!isSuccess) {
    wx.toast({ title: '网络异常请重试', icon: 'error' })
    return response
  }

  // 判断服务器响应的状态码
  switch (data.code) {
    // 请求成功
    case 200:
      // 请求成功返回解构数据
      return data

    case 201:
      return Promise.reject(response)

    // token问题
    case 208:
      const res = await wx.model({ content: '鉴权失败，请重新登陆', showCancel: false })

      if (res) {
        // 数据清除
        clearStorage()

        // 跳转到登录页
        wx.navigateTo({
          url: '/pages/login/login'
        })
      }

      return Promise.reject(response)

    case 100:
      const res2 = await wx.model({ content: '鉴权失败，请重新登陆', showCancel: false })

      if (res2) {
        // 数据清除
        clearStorage()

        // 跳转到登录页
        wx.navigateTo({
          url: '/pages/login/login'
        })
      }

      return Promise.reject(response)

    // 其他问题
    default:
      wx.toast({ title: '程序出现异常，请联系客服或稍后重试' })
      return Promise.reject(response)
  }
}

// 将 WxRequest 实例对外暴露
export default instance
